package com.edu.zhku.wms.utils;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.edu.zhku.wms.common.Consts;
import com.edu.zhku.wms.config.JwtConfig;
import com.edu.zhku.wms.exception.BaseException;
import com.edu.zhku.wms.vo.UserPrincipal;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author Feng
 * @since 2021/10/28 14:35
 */
@EnableConfigurationProperties(JwtConfig.class)
@Configuration
public class JwtUtil {

    @Autowired
    private JwtConfig jwtConfig;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 创建JWT
     *
     * @param rememberMe  记住我
     * @param id          用户id
     * @param subject     用户名
     * @param authorities 用户权限列表
     * @return JWT
     */
    public String createJWT(Boolean rememberMe, Integer id, String subject, Collection<? extends GrantedAuthority> authorities) {
        Date now = new Date();
        JwtBuilder builder = Jwts.builder()
                .setId(id.toString()) // 设置Payload中的jti(JWT ID)编号，我们放用户ID
                .setSubject(subject) // 设置Payload中的sub(Subject)主题，我们放用户名
                .setIssuedAt(now) // 设置Payload中的iat(Issued At)签发时间
                .signWith(SignatureAlgorithm.HS256, jwtConfig.getKey()) // 设置Header部分的alg签名算法以及Signature部分
                .claim("authorities", authorities); // 在Payload中自定义私有字段

        // 设置过期时间
        Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl();
//        if (ttl > 0) {
//            builder.setExpiration(new Date(now.getTime() + ttl));
//        }

        String jwt = builder.compact(); // 生成JWT字符串
        // 将生成的JWT保存至Redis
        stringRedisTemplate.opsForValue().set(Consts.REDIS_JWT_KEY_PREFIX + subject, jwt, ttl, TimeUnit.MILLISECONDS);
        return jwt;
    }

    /**
     * 创建JWT
     *
     * @param authentication 用户认证信息
     * @param rememberMe     记住我
     * @return JWT
     */
    public String createJWT(Authentication authentication, Boolean rememberMe) {
        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
        return createJWT(rememberMe, userPrincipal.getId(), userPrincipal.getUsername(), userPrincipal.getAuthorities());
    }

    /**
     * 解析JWT
     *
     * @param jwt JWT
     * @return {@link Claims}
     */
    public Claims parseJWT(String jwt) {
        Claims claims = Jwts.parser().setSigningKey(jwtConfig.getKey()).parseClaimsJws(jwt).getBody();

        String username = claims.getSubject();

        // 校验redis中的JWT是否存在
        if (isTokenExpired(username)) {
            throw new BaseException("token已过期");
        }
        return claims;
    }

    /**
     * 设置JWT过期
     *
     * @param request 请求
     */
    public void invalidateJWT(HttpServletRequest request) {
        String jwt = getJwtFromRequest(request);
        String username = getUsernameFromJWT(jwt);
        // 从redis中清除JWT
        stringRedisTemplate.delete(Consts.REDIS_JWT_KEY_PREFIX + username);
    }

    /**
     * 根据 jwt 获取用户名
     *
     * @param jwt JWT
     * @return 用户名
     */
    public String getUsernameFromJWT(String jwt) {
        Claims claims = parseJWT(jwt);
        return claims.getSubject();
    }

    /**
     * 从 request 的 header 中获取 JWT
     *
     * @param request 请求
     * @return JWT
     */
    public String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader(Consts.REQ_JWT_HEADER_KEY);
        if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(Consts.REQ_JWT_HEADER_VALUE_PREFIX)) {
            return bearerToken.substring(Consts.REQ_JWT_HEADER_VALUE_PREFIX.length());
        }
        return null;
    }

    /**
     * 验证token是否还有效
     *
     * @param token       客户端传入的token
     * @param userDetails 从数据库中查询出来的用户信息
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUsernameFromJWT(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(username);
    }

    /**
     * jwt续期
     */
    public void checkRenewal(String username) {
        String redisKey = Consts.REDIS_JWT_KEY_PREFIX + username;
        if (!isTokenExpired(username) && stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS) < jwtConfig.getTtl()) {
            stringRedisTemplate.expire(redisKey, jwtConfig.getTtl(), TimeUnit.MILLISECONDS);
        }
    }

    /**
     * jwt是否已过期
     */
    private boolean isTokenExpired(String username) {
        Long expire = stringRedisTemplate.getExpire(Consts.REDIS_JWT_KEY_PREFIX + username, TimeUnit.MILLISECONDS);
        return Objects.isNull(expire) || expire <= 0;
    }
}
